#ifndef BEZIER_H
#define BEZIER_H

#include <cassert>
#include <algorithm>
#include <vector>

#include "Curves/Interfaces/icurve.h"
#include "Curves/CurveUtilities.h"

template< class IPoint >
class Bezier : public ICurve< IPoint >
{

protected:
    /* Fields. */
    int degree;

public:
    /* Constructors. */
    Bezier( );
    /* Destructors. */
    ~Bezier( );

    /* Methods. */
    void AddPoint( const IPoint& point, double weight );
    void AddPoint( const IPoint& point, int point_index, double weight );

    void RemovePoint( );
    void RemovePoint( int point_index );

    IPoint Eval( double t ) const;
    vector< IPoint > ComputePoints( int n );

    void ElevateDegree( int n );

    void ReduceDegree1( ); // Metoda "odwrotna" do podnoszenia stopnia.
    void ReduceDegree2( ); // Interpolacja Hermite'a.

    void JoinC1( Bezier< IPoint >& bcurve, BezierJoinType type );
    void JoinC2( Bezier< IPoint >& bcurve, BezierJoinType type );

    Bezier< IPoint >* Split( double t );

};

/* Constructor. */
template< class IPoint >
Bezier< IPoint >::Bezier( )
{
    degree = -1;
}

/* Destructor. */
template< class IPoint >
Bezier< IPoint >::~Bezier( )
{
}

/* Methods. */
template< class IPoint >
void Bezier< IPoint >::AddPoint( const IPoint& point, double weight = 1.0 )
{
    ICurve< IPoint >::AddPoint( point );
    degree++;
}

template< class IPoint >
void Bezier< IPoint >::AddPoint( const IPoint& point, int point_index, double weight = 1.0 )
{
    ICurve< IPoint >::AddPoint( point, point_index );
    degree++;
}

template< class IPoint >
void Bezier< IPoint >::RemovePoint( )
{
    ICurve< IPoint >::RemovePoint( );
    degree--;
}

template< class IPoint >
void Bezier< IPoint >::RemovePoint( int point_index )
{
    ICurve< IPoint >::RemovePoint( point_index );
    degree--;
}

template< class IPoint >
IPoint Bezier< IPoint >::Eval( double t ) const
{
    assert( 0.0 <= t && t <= 1.0 );

    IPoint P[degree + 1];

    for ( int i = 0; i <= degree; i++ )
        P[i] = this -> control_points[i];

    for ( int k = 1; k <= degree; k++ )
        for ( int i = 0; i <= degree - k; i++ )
            P[i] = ( 1.0 - t ) * P[i] + t * P[i + 1];

    return P[0];
}

template< class IPoint >
vector< IPoint > Bezier< IPoint >::ComputePoints( int n )
{
    if ( this -> computed_points_up_to_date &&
         this -> computed_points.size( ) == static_cast< unsigned int >( n ) )
        return this -> computed_points;

    this -> computed_points.resize( n );

    for ( int i = 0; i < n; i++ )
        this -> computed_points[i] = Eval( static_cast< double >( i ) / ( n - 1 ) );

    this -> computed_points_up_to_date  = true;

    return this -> computed_points;
}

template< class IPoint >
void Bezier< IPoint >::ReduceDegree1( )
{
    if ( degree < 1 )
        return;

    vector< IPoint > new_points( degree );

    new_points[0] = this -> control_points[0];
    int n         = degree - 1;
    int m         = degree;

    if ( n % 2 == 0 )
    {
        int    h = n / 2;
        IPoint q;

        for ( int i = 1; i <= h; i++ )
            new_points[i] = ( m * this -> control_points[i] - i * ( new_points[i-1]) ) / (m - 1);

        q = this -> control_points[h];

        new_points[n] = this -> control_points[m];

        for ( int i = n; i >= h + 1; i-- )
            new_points[i-1] = ( m * this -> control_points[i] - (m - i) * new_points[i] ) / i;

        new_points[h] = (new_points[h] + q) / 2;
    }

    else
    {
        int h = (n - 1) / 2;

        for ( int i = 1; i <= h; i++ )
            new_points[i] = ( m * this -> control_points[i] - i * ( new_points[i-1]) ) / (m - 1);

        new_points[n] = this -> control_points[m];

        for ( int i = n; i >= h + 2; i-- )
            new_points[i-1] = ( m * this -> control_points[i] - (m - i) * new_points[i] ) / i;
    }

    this -> control_points.pop_back();
    this -> control_points = new_points;
    degree--;

    this -> computed_points_up_to_date = false;
    this -> convex_hull_up_to_date     = false;
}

template< class IPoint >
void Bezier< IPoint >::ReduceDegree2( )
{

}

template< class IPoint >
void Bezier< IPoint >::JoinC1( Bezier< IPoint >& bcurve, BezierJoinType type = LF )
{
    switch ( type )
    {
        case FL :
        break;

    case FF :
        break;

    case LF :
        break;

    case LL :
        break;
    }

    IPoint qn  = this -> control_points[degree];
    IPoint qn1 = this -> control_points[degree-1];

    vector< IPoint > c_points( bcurve.ControlPoints( ).size( ) );
    reverse( c_points.begin( ), c_points.end( ) );

    c_points    = bcurve.ControlPoints( );
    c_points[0] = 2 * qn - qn1;

    typename vector< IPoint > :: iterator it;

    for ( it = c_points.begin( ); it != c_points.end( ); ++it )
        AddPoint( *it );

    this -> computed_points_up_to_date = false;
    this -> convex_hull_up_to_date     = false;
}

template< class IPoint >
void Bezier< IPoint >::JoinC2( Bezier< IPoint >& bcurve, BezierJoinType type = LF )
{
    IPoint qn  = this -> control_points[degree];
    IPoint qn1 = this -> control_points[degree-1];

    vector< IPoint > c_points( bcurve.ControlPoints( ).size( ) );

    c_points                       = bcurve.ControlPoints( );
    c_points[0]                    = 2 * qn1 / 3 + c_points[1] / 3;
    c_points[1]                    = 4 * qn1 / 3 + 2 * c_points[1] / 3 - qn;

    typename vector< IPoint > :: iterator it;

    for ( it = c_points.begin( ); it != c_points.end( ); ++it )
        AddPoint( *it );

    this -> computed_points_up_to_date = false;
    this -> convex_hull_up_to_date     = false;
}

template< class IPoint >
void Bezier< IPoint >::ElevateDegree( int n )
{
    for ( int i = 0; i < n; i++ )
    {
        int m = degree + 1;
        this -> control_points.push_back( this -> control_points[degree] );

        for ( int i = degree; i > 0; i-- )
            this -> control_points[i] = ( i    * this -> control_points[i-1] +
                                         (m-i) * this -> control_points[i] ) / m;

        degree++;
        this -> convex_hull_up_to_date     = false;
    }
}

template< class IPoint >
Bezier<IPoint>* Bezier< IPoint >::Split( double t )
{
    assert( t >= 0.0 && t <= 1.0 );

    Bezier< IPoint >* bcurve = new Bezier( );

    bcurve -> AddPoint( this -> control_points[0] );

    for ( int j = 1; j <= degree; j++ )
    {
        for ( int i = 0; i <= degree - j; i++ )
            this -> control_points[i] = (1.0 - t) * this -> control_points[i] +
                                         t        * this -> control_points[i+1];

        bcurve -> AddPoint( this -> control_points[0] );
    }

    this -> computed_points_up_to_date = false;
    this -> convex_hull_up_to_date     = false;

    return bcurve;
}

#endif // BEZIER_H
